﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Fasterflect;
using Fasterflect.Probing;
using ILCodeExplorer;
using PatternExplorer.DerivedConcepts;
using PatternExplorer.DerivedContainers;
using NLog;

namespace PatternExplorer
{
    public class TypeExplorer
    {
        private readonly NLog.Logger logger = LogManager.GetCurrentClassLogger();
        public TypeSummary currentTypeSummary; 
        public List<Type> GenericArguments;

        /*
         * I don't think this can be done with "regular" reflection, since usages cannot be detected by looking only at the structure of the classes. 
         * I guess you'll need to disassemble the IL and analyze it, looking for call, calli, and callvirt instructions (property lookups are also method calls). 
         * You can get the IL for a method with type.GetMethod("Method").GetMethodBody().GetILAsByteArray() 
         */

        public List<Type> BaseTypes
        { get; set; }

        private static bool HasOpCodesBeenLoaded = false;

        public TypeExplorer(Type aType)
        {
            if (aType.IsGenericType)
            {
                GenericArguments = new List<Type>(aType.GetGenericArguments());
                // currentTypeSummary.IsGeneric = aType.IsGenericType;
                if (GenericArguments.Count == 1)
                {
                    currentTypeSummary = new SingleGenericTypeSummary();
                    var baseType = GenericArguments[0].BaseType;
                    if (baseType != null)
                        ((SingleGenericTypeSummary)currentTypeSummary).BaseGenericArgumentType = baseType.FullName;
                    else
                    {
                        logger.Debug("Error: The generic type is not identified in  " + aType.FullName);
                    }
                }
                else
                {
                    currentTypeSummary = new MultipleGenericTypeSummary();
                    List<string> genericTypes = new List<string>();
                    bool allBaseTypesDefined = true;
                    foreach(Type aGenericType in GenericArguments)
                    {
                        if (aGenericType.BaseType != null)
                            genericTypes.Add(aGenericType.BaseType.FullName);
                        else
                            allBaseTypesDefined = false;
                    }
                    if (allBaseTypesDefined)
                         ((MultipleGenericTypeSummary) currentTypeSummary).BaseGenericArgumentTypes = genericTypes;
                    else
                    {
                        logger.Debug("Error: some of the generic types are not identified in  " + aType.FullName);
                    }
                }
                string strGenericTypeDesc = aType.FullName;
                int genericMarkerPosition = strGenericTypeDesc.IndexOf("`", System.StringComparison.Ordinal);
                if (genericMarkerPosition != -1)
                    currentTypeSummary.Name = strGenericTypeDesc.Substring(0, genericMarkerPosition);
                else
                {
                    logger.Debug("Error: generic type name invalid " + aType.FullName);
                }
            }
            else
            {
                currentTypeSummary = new TypeSummary();
                currentTypeSummary.Name = aType.FullName;
            }

            currentTypeSummary.IsAbstract = aType.IsAbstract;
            currentTypeSummary.IsInterface = aType.IsInterface;
            

            if (!HasOpCodesBeenLoaded)
            {
                Globals.LoadOpCodes();
                HasOpCodesBeenLoaded = true;
            }
            
            BaseTypes = GetInheritanceTree(aType);
            TypeSummarySingletonContainer.Instance.AddInheritanceStack(BaseTypes);
            
 
            foreach (PropertyInfo aPropertyInfo in aType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (!currentTypeSummary.PublicProperties.Contains(aPropertyInfo.Name))
                {
                    PropertySummary aNewPropertySummary = new PropertySummary();
                    aNewPropertySummary.IsStatic = aPropertyInfo.IsStatic();
                    aNewPropertySummary.Name = aPropertyInfo.Name;
                    aNewPropertySummary.ReturnTypeName = aPropertyInfo.PropertyType.FullName;
                    currentTypeSummary.PublicProperties.Add(aNewPropertySummary);
                }
            }
            
            foreach(MethodInfo aMethodInfo in aType.GetMethods(BindingFlags.Instance | BindingFlags.Public))
            {
                MethodSummary aMethodSummary = ProcessMethodInfo(aMethodInfo);
                if (aMethodSummary != null)
                    currentTypeSummary.PublicMethods.Add(aMethodSummary);
            }

            foreach (MethodInfo aMethodInfo in aType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic))
            {
                MethodSummary aMethodSummary = ProcessMethodInfo(aMethodInfo);
                if (aMethodSummary != null)
                    currentTypeSummary.PrivateMethods.Add(aMethodSummary);
            }
            TypeSummarySingletonContainer.Instance.Add(currentTypeSummary);
        }

        private MethodSummary ProcessMethodInfo(MethodInfo aMethodInfo)
        {
            if (!(aMethodInfo.DeclaringType.Namespace.StartsWith("System")))
            {
                MethodSummary aNewMethodSummary = new MethodSummary();
                aNewMethodSummary.Name = aMethodInfo.Name;
                aNewMethodSummary.IsPublicMethod = true;
                aNewMethodSummary.IsStaticMethod = aMethodInfo.IsStatic;
                if (aMethodInfo.ReturnType.FullName != "System.Void")
                {
                    if ((aMethodInfo.Name.StartsWith("get_") | (aMethodInfo.Name.StartsWith("set_"))))
                    {
                        string strRemainingMethodName = aMethodInfo.Name.Substring(4);
                        if (!currentTypeSummary.PublicMethods.Contains(strRemainingMethodName))
                        {
                            aNewMethodSummary.ReturnTypeName = aMethodInfo.ReturnType.FullName;
                        }
                    }
                    else if (!currentTypeSummary.PublicMethods.Contains(aMethodInfo.Name))
                    {
                        aNewMethodSummary.ReturnTypeName = aMethodInfo.ReturnType.FullName;
                        
                    }
                }
                else
                {
                    aNewMethodSummary.ReturnTypeName = "";
                    aNewMethodSummary.IsVoidMethod = true;
                }
                ILCodeExplorer.MethodBodyReader ilMethodCodeReader = new MethodBodyReader(aMethodInfo);
                aNewMethodSummary.MethodsExternalCallsILCode = ilMethodCodeReader.GetFunctionCallDescriptions();
                return aNewMethodSummary;
            }
            return null;
        }


        private static List<Type> GetInheritanceTree(Type type)
        {
            var list = new List<Type>();

            if (type.IsClass)
            {
                list.Add(type);
                if (type.BaseType != null)
                {
                    foreach (Type aType in GetInheritanceTree(type.BaseType))
                    {
                        list.Add(aType);
                    }
                }
            }

            return list;
        }
    }
}
